/*
 * Copyright 2021, OpenRemote Inc.
 *
 * See the CONTRIBUTORS.txt file in the distribution for a
 * full listing of individual contributors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.openremote.agent.protocol.bluetooth.mesh;

import org.openremote.agent.protocol.bluetooth.mesh.utils.MeshAddress;

import java.util.UUID;

public class Group {

    public int id = 0;

    private String name = "Mesh Group";

    private int address;

    private UUID addressLabel;

    private int parentAddress;

    private UUID parentAddressLabel;

    private String meshUuid;

    /**
     * Constructs a mesh group to be used for room db implementation
     *
     * @param id                 Autogenerated primary key id
     * @param address            Address of the group
     * @param addressLabel       Label UUID of the group address in the case of a virtual address
     * @param parentAddress      Parent address of the group
     * @param parentAddressLabel Label UUID of the parent address in the case of a virtual address
     * @param meshUuid           UUID of the mesh network
     */
    public Group(final int id, final int address,
                 final UUID addressLabel,
                 final int parentAddress,
                 final UUID parentAddressLabel,
                 final String meshUuid) throws IllegalArgumentException {
        this(address, addressLabel, parentAddress, parentAddressLabel, meshUuid);
        this.id = id;
    }

    /**
     * Constructs a mesh group
     *
     * @param address  Address of the group
     * @param meshUuid UUID of the mesh network
     */
    public Group(final int address, final String meshUuid) throws IllegalArgumentException {
        this(address, null, MeshAddress.UNASSIGNED_ADDRESS, null, meshUuid);
    }

    /**
     * Constructs a mesh group
     *
     * @param address       Address of the group
     * @param parentAddress Parent address of the group. If there is no parent group use {@link MeshAddress#UNASSIGNED_ADDRESS}
     * @param meshUuid      UUID of the mesh network
     */
    public Group(final int address, final int parentAddress, final String meshUuid) throws IllegalArgumentException {
        this(address, null, parentAddress, null, meshUuid);
    }

    /**
     * Constructs a mesh group
     *
     * @param address            Address of the group
     * @param parentAddressLabel Label UUID of the parent address in the case of a virtual address
     * @param meshUuid           UUID of the mesh network
     */
    public Group(final int address, final UUID parentAddressLabel, final String meshUuid) throws IllegalArgumentException {
        this(address, null, MeshAddress.UNASSIGNED_ADDRESS, parentAddressLabel, meshUuid);
    }

    /**
     * Constructs a mesh group
     *
     * @param addressLabel  Label UUID of the address in the case of a virtual address
     * @param parentAddress Parent address of the group. If there is no parent group use {@link MeshAddress#UNASSIGNED_ADDRESS}
     * @param meshUuid      UUID of the mesh network
     */
    public Group(final UUID addressLabel, final int parentAddress, final String meshUuid) throws IllegalArgumentException {
        this(MeshAddress.UNASSIGNED_ADDRESS, addressLabel, parentAddress, null, meshUuid);
    }

    /**
     * Constructs a mesh group
     *
     * @param addressLabel       Label UUID of the group address in the case of a virtual address
     * @param parentAddressLabel Label UUID of the parent address in the case of a virtual address
     * @param meshUuid           UUID of the mesh network
     */
    public Group(final UUID addressLabel, final UUID parentAddressLabel, final String meshUuid) throws IllegalArgumentException {
        this(MeshAddress.UNASSIGNED_ADDRESS, addressLabel, MeshAddress.UNASSIGNED_ADDRESS, parentAddressLabel, meshUuid);
    }

    /**
     * Constructs a mesh group
     *
     * @param address            Address of the group
     * @param addressLabel       Label UUID of the group address in the case of a virtual address
     * @param parentAddress      Parent address of the group. If there is no parent group use {@link MeshAddress#UNASSIGNED_ADDRESS}
     * @param parentAddressLabel Label UUID of the parent address in the case of a virtual address
     * @param meshUuid           UUID of the mesh network
     * @throws IllegalArgumentException for invalid arguments
     */
    private Group(final int address,
                  final UUID addressLabel,
                  final int parentAddress,
                  final UUID parentAddressLabel,
                  final String meshUuid) throws IllegalArgumentException {
        if (!MeshAddress.isValidUnassignedAddress(address) && MeshAddress.isValidUnassignedAddress(parentAddress)) {
            if (address == parentAddress) {
                throw new IllegalArgumentException("The value of the parentAddress property shall not be equal to the value of the address property");
            }
        }

        if (addressLabel == null) {
            if (MeshAddress.isValidVirtualAddress(address)) {
                throw new IllegalArgumentException("Please provide a Label UUID when creating groups using virtual addresses");
            } else if (!MeshAddress.isValidGroupAddress(address)) {
                throw new IllegalArgumentException("Address of the group must be a valid group address or a virtual address");
            }
            this.address = address;
            generateParentAddressLabel(parentAddressLabel, parentAddress);
        } else {
            this.addressLabel = addressLabel;
            this.address = MeshAddress.generateVirtualAddress(addressLabel);
            generateParentAddressLabel(parentAddressLabel, parentAddress);
        }
        this.meshUuid = meshUuid;
    }

    private void generateParentAddressLabel(final UUID parentAddressLabel, final int parentAddress) throws IllegalArgumentException {
        this.parentAddressLabel = parentAddressLabel;
        if (parentAddressLabel == null) {
            if (MeshAddress.isValidVirtualAddress(parentAddress)) {
                throw new IllegalArgumentException("Please provide a Label UUID for the parent address of this group");
            } else if (!MeshAddress.isValidGroupAddress(parentAddress) && !MeshAddress.isValidUnassignedAddress(parentAddress)) {
                throw new IllegalArgumentException("parentAddress value must be a group address, " +
                    "virtual address or an unassigned address");
            }
            this.parentAddress = parentAddress;
        } else {
            this.parentAddress = MeshAddress.generateVirtualAddress(parentAddressLabel);
        }
    }

    /**
     * Returns the provisionerUuid of the network
     *
     * @return provisionerUuid
     */
    public String getMeshUuid() {
        return meshUuid;
    }

    /**
     * Sets the provisionerUuid of the network
     *
     * @param meshUuid network provisionerUuid
     */
    public void setMeshUuid(final String meshUuid) {
        this.meshUuid = meshUuid;
    }

    /**
     * Returns the address of the group
     *
     * @return 2 byte group address
     */
    public int getAddress() {
        return address;
    }

    /**
     * Returns the label UUID of the address of the group
     *
     * @return Label UUID of the group address if the group address is a virtual address or null otherwise
     */
    public UUID getAddressLabel() {
        return addressLabel;
    }

    /**
     * Sets the group address label
     *
     * @param uuidLabel UUID label of the address
     */
    public void setAddressLabel(final UUID uuidLabel) {
        addressLabel = uuidLabel;
        if (uuidLabel != null) {
            address = MeshAddress.generateVirtualAddress(uuidLabel);
        }
    }

    public UUID getParentAddressLabel() {
        return parentAddressLabel;
    }

    /**
     * Sets the label UUID of the parent address
     *
     * @param parentAddressLabel Label UUID of the parent address
     */
    public void setParentAddressLabel(final UUID parentAddressLabel) {
        this.parentAddressLabel = parentAddressLabel;
        if (parentAddressLabel != null) {
            parentAddress = MeshAddress.generateVirtualAddress(parentAddressLabel);
        }
    }

    /**
     * Returns address of the parent group if the group has one
     *
     * @return parent address
     */
    public int getParentAddress() {
        return parentAddress;
    }

    /**
     * Sets the parent address, if this group belongs to a parent group
     *
     * @param parentAddress address of the parent group
     */
    public void setParentAddress(final int parentAddress) {
        if (MeshAddress.isValidVirtualAddress(parentAddress)) {
            throw new IllegalArgumentException("Assigning a virtual address to parentAddress can be done only by setting a parent address label");
        } else if (!MeshAddress.isValidGroupAddress(parentAddress)) {
            throw new IllegalArgumentException("parentAddress must be a group address value");
        }
        this.parentAddress = parentAddress;
        this.parentAddressLabel = null;
    }

    /**
     * Returns the group name of a mesh network
     */
    public String getName() {
        return name;
    }

    /**
     * Sets a name to a mesh group
     */
    public void setName(final String name) {
        this.name = name;
    }
}
